#ifndef GAMEENGINE_CORE_DATA_ELEMENT_HPP
#define GAMEENGINE_CORE_DATA_ELEMENT_HPP

#include <GameEngine/Core/Memory/memory_manager.h>

#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

//=================================================================================
// Data element class

//! Default constructor
template <class R, class W>
DataElementBase<R,W>::DataElementBase() {}

//! Destructor
template <class R, class W>
DataElementBase<R,W>::~DataElementBase() {}

//! Get the data type code
/*!
 @return The data type code
 */
template <class R, class W>
DataType DataElementBase<R,W>::data_type() const {
	return DataType::Unknown;
}




//=================================================================================
// Implementation of the data element class

//! Default constructor
/*!
 @note Class T should have a default constructor
 */
template <class T, class R, class W>
DataElement<T,R,W>::DataElement() : DataElementBase() {}

//! Constructor from a data item
/*!
 @param item The data item to copy
 @note Class T should have a copy constructor
 */
template <class T, class R, class W>
DataElement<T,R,W>::DataElement(const DataItem<T>& item) : element_(item.element()) {}

//! Copy constructor
/*!
 @param rhs The data element to copy
 @note Class T should have a copy constructor
 */
template <class T, class R, class W>
DataElement<T,R,W>::DataElement(const DataElement& rhs) :
	element_(rhs.element_) {}

//! Destructor
/*!
 @note The destructor of class T should be public
 */
template <class T, class R, class W>
DataElement<T,R,W>::~DataElement() {}

//! Assignment operator
/*!
 @param rhs The data element to copy
 @note Class T should have an assignment operator
 */
template <class T, class R, class W>
DataElement<T,R,W>& DataElement<T,R,W>::operator=(const DataElement& rhs) {
	if ( &rhs != this ) {
		element_ = rhs.element_;
	}
	return *this;
}

//! Accessor to the element (read-write)
/*!
 @return A reference to the element
 */
template <class T, class R, class W>
T& DataElement<T,R,W>::element() {
	return element_;
}

//! Accessor to the element (read only)
/*!
 @return A constant reference to the element
 */
template <class T, class R, class W>
const T& DataElement<T,R,W>::element() const {
	return element_;
}

//! Convert the element to the desired format
/*!
 @param converted The value that will receive the conversion
 @note Class C should have an implicit conversion to class T
 */
template <class T, class R, class W>
template <class C>
void DataElement<T,R,W>::convert_to(C& converted) const {
	converted = element_;
}

//! Get the size of the element in bits
/*!
 @return The size of the element in bits
 */
template <class T, class R, class W>
int DataElement<T,R,W>::bits() const {
	return sizeof(T);
}

//! Get the data type code
/*!
 @return The data type code
 */
template <class T, class R, class W>
DataType DataElement<T,R,W>::data_type() const {
	return DataTypes<T>::get_type();
}

//! Read the element from a given reader
/*!
 @param reader The reader from which element will be read
 @return True if element was succesfully read, false otherwise
 @note Reader should have a read_element(T&) method
 */
template <class T, class R, class W>
bool DataElement<T,R,W>::read_element(R& reader) {
	return reader.read_element(element_);
}

//! Write the element in the given writer
/*!
 @param writer The writer to which element will be written
 @return True if element was succesfully written, false otherwise
 @note Writer should have a write_element(const T&) method
 */
template <class T, class R, class W>
bool DataElement<T,R,W>::write_element(W& writer) const {
	return writer.write_element(element_);
}


//=================================================================================
// Composite data element class

//! Default constructor
template <class R, class W>
DataElementComposite<R,W>::DataElementComposite() {}

//! Copy constructor
/*!
 @param rhs The composite element to copy
 */
template <class R, class W>
DataElementComposite<R,W>::DataElementComposite(const DataElementComposite& rhs) {
	for ( QList<DataElementBase<R,W>*>::const_iterator it = rhs.elements_.begin(); it != rhs.elements_.end(); ++it ) {
		elements_.append((*it)->clone());
	}
}

//! Destructor
/*!
 *  Deletes all the clones
 */
template <class R, class W>
DataElementComposite<R,W>::~DataElementComposite() {
	for ( QList<DataElementBase<R,W>*>::const_iterator it = elements_.begin(); it != elements_.end(); ++it ) {
		MemoryManager::instance()->desallocate(*it);
	}
}

//! Assignment operator
/*!
 @param rhs The composite element to copy
 @return A reference to this element
 */
template <class R, class W>
DataElementComposite<R,W>& DataElementComposite<R,W>::operator=(const DataElementComposite& rhs) {
	if ( &rhs != this ) {
		for ( QList<DataElementBase<R,W>*>::const_iterator it = rhs.elements_.begin(); it != rhs.elements_.end(); ++it ) {
			elements_.append((*it)->clone());
		}
	}
	return *this;
}

//! Add an element to the list of elements
/*!
 @param element The element to add
 @return True if element could be added, false otherwise
 */
template <class R, class W>
template <class T>
bool DataElementComposite<R,W>::add_element(const DataElement<T,R,W>& element) {
	elements_.append(MemoryManager::instance()->allocate(element));
	return true;
}

//! Add an element to the list of elements
/*!
 @param type The type of element to add
 @return True if element could be added, false otherwise
 @note Unknown, custom and composite types cannot be added
       using that function, because of the lack of
	   information on their data type. Custom types can
	   however be added using the overloaded add_element
	   function.
 */
template <class R, class W>
bool DataElementComposite<R,W>::add_element(DataType type) {
	if ( type == Boolean ) elements_.append(MemoryManager::instance()->allocate< DataElement<bool,R,W> >());
	else if ( type == ShortInteger ) elements_.append(MemoryManager::instance()->allocate< DataElement<short,R,W> >());
	else if ( type == UnsignedShortInteger ) elements_.append(MemoryManager::instance()->allocate< DataElement<unsigned short,R,W> >());
	else if ( type == Integer ) elements_.append(MemoryManager::instance()->allocate< DataElement<int,R,W> >());
	else if ( type == UnsignedInteger ) elements_.append(MemoryManager::instance()->allocate< DataElement<unsigned int,R,W> >());
	else if ( type == LongInteger ) elements_.append(MemoryManager::instance()->allocate< DataElement<long,R,W> >());
	else if ( type == UnsignedLongInteger ) elements_.append(MemoryManager::instance()->allocate< DataElement<unsigned long,R,W> >());
	else if ( type == Char ) elements_.append(MemoryManager::instance()->allocate< DataElement<char,R,W> >());
	else if ( type == UnsignedChar ) elements_.append(MemoryManager::instance()->allocate< DataElement<unsigned char,R,W> >());
	else if ( type == Float ) elements_.append(MemoryManager::instance()->allocate< DataElement<float,R,W> >());
	else if ( type == Double ) elements_.append(MemoryManager::instance()->allocate< DataElement<double,R,W> >());
	else if ( type == LongDouble ) elements_.append(MemoryManager::instance()->allocate< DataElement<long double,R,W> >());
	else return false;
	return true;
}

//! Accessor to the list of elements (read only)
/*!
 @return A constant reference to the list of elements
 */
template <class R, class W>
const QList<DataElementBase<R,W>*>& DataElementComposite<R,W>::elements() const {
	return elements_;
}

//! Get the size of the composite element in bits
/*!
 @return The size of the composite element in
         bits. This is the sum of the sizes of
		 all the elements contained by the
		 composite element.
 */
template <class R, class W>
int DataElementComposite<R,W>::bits() const {
	int res = 0;
	for ( QList<DataElementBase<R,W>*>::const_iterator it = elements_.begin(); it != elements_.end(); ++it ) {
		res += (*it)->bits();
	}
	return res;
}

//! Get the data type code
/*!
 @return The data type code
 */
template <class R, class W>
DataType DataElementComposite<R,W>::data_type() const {
	return DataType::Composite;
}

//! Read the composite element from a given reader
/*!
 @param reader The reader from which composite element will be read
 @return True if composite element was succesfully read, false otherwise
 @note This function sequentially reads all the elements contained
       by the composite element
 @note Reader should have a read_element(T&) method
 */
template <class R, class W>
bool DataElementComposite<R,W>::read_element(R& reader) {
	for ( QList<DataElementBase<R,W>*>::iterator it = elements_.begin(); it != elements_.end(); ++it ) {
		if ( !(*it)->read_element(reader) ) return false;
	}
	return true;
}

//! Write the composite element in a given writer
/*!
 @param writer The writer in which composite element will be written
 @return True if composite element was succesfully written, false otherwise
 @note This function sequentially writes all the elements contained
       by the composite element
 @note Writer should have a write_element(const T&) method
 */
template <class R, class W>
bool DataElementComposite<R,W>::write_element(W& writer) const {
	for ( QList<DataElementBase<R,W>*>::const_iterator it = elements_.begin(); it != elements_.end(); ++it ) {
		if ( !(*it)->write_element(writer) ) return false;
	}
	return true;
}


//=================================================================================
// Data item class

//! Default constructor
/*!
 @note Class T should have a default constructor
 */
template <class T>
DataItem<T>::DataItem() {}

//! Build a data item from the corresponding data element
/*!
 @param element The data element to copy
 @note Class T should have a copy constructor
 */
template <class T>
template<class R, class W>
DataItem<T>::DataItem(const DataElement<T,R,W>& element) : element_(element.element()) {}

//! Copy constructor
/*!
 @param rhs The data item to copy
 @note Class T should have a copy constructor
 */
template <class T>
DataItem<T>::DataItem(const DataItem& rhs) :
	element_(rhs.element_) {}

//! Destructor
/*!
 @note The destructor of class T should be public
 */
template <class T>
DataItem<T>::~DataItem() {}

//! Assignment operator
/*!
 @param rhs The data item to copy
 @note Class T should have an assignment operator
 */
template <class T>
DataItem<T>& DataItem<T>::operator=(const DataItem& rhs) {
	if ( &rhs != this ) {
		element_ = rhs.element_;
	}
	return *this;
}

//! Accessor to the element (read-write)
/*!
 @return A reference to the element
 */
template <class T>
T& DataItem<T>::element() {
	return element_;
}

//! Accessor to the element (read only)
/*!
 @return A constant reference to the element
 */
template <class T>
const T& DataItem<T>::element() const {
	return element_;
}

//! Convert the element to the desired format
/*!
 @param converted The value that will receive the conversion
 @note Class C should have an implicit conversion to class T
 */
template <class T>
template <class C>
void DataItem<T>::convert_to(C& converted) const {
	converted = element_;
}

//! Get the size of the element in bits
/*!
 @return The size of the element in bits
 */
template <class T>
int DataItem<T>::bits() const {
	return sizeof(T);
}

//! Get the data type code
/*!
 @return The data type code
 */
template <class T>
DataType DataItem<T>::data_type() const {
	return DataTypes<T>::get_type();
}

//! Read the element from a given reader
/*!
 @param reader The reader from which element will be read
 @return True if element was succesfully read, false otherwise
 @note Reader should have a read_element(T&) method
 */
template <class T>
template <class R>
bool DataItem<T>::read_element(R& reader) {
	return reader.read_element(element_);
}

//! Write the element in the given writer
/*!
 @param writer The writer to which element will be written
 @return True if element was succesfully written, false otherwise
 @note Writer should have a write_element(const T&) method
 */
template <class T>
template <class W>
bool DataItem<T>::write_element(W& writer) const {
	return writer.write_element(element_);
}

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif

#endif
